
/*-------------------------------------------------------------------------
 *
 * tableam.h
 *	  POSTGRES 表访问方法定义。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/access/tableam.h
 *
 * NOTES
 *		请参阅 tableam.sgml 以获取更高层次的文档。
 *
 *-------------------------------------------------------------------------
 */
#ifndef TABLEAM_H
#define TABLEAM_H

#include "access/relscan.h"
#include "access/sdir.h"
#include "access/xact.h"
#include "utils/guc.h"
#include "utils/rel.h"
#include "utils/snapshot.h"


#define DEFAULT_TABLE_ACCESS_METHOD	"heap"

/* GUCs */
extern PGDLLIMPORT char *default_table_access_method;
extern PGDLLIMPORT bool synchronize_seqscans;


struct BulkInsertStateData;
struct IndexInfo;
struct SampleScanState;
struct TBMIterateResult;
struct VacuumParams;
struct ValidateIndexState;

/*
 * scan_begin 回调的标志参数的位掩码值。
 */
typedef enum ScanOptions
{
	/* 可以指定 SO_TYPE_* 之一 */
	SO_TYPE_SEQSCAN = 1 << 0,
	SO_TYPE_BITMAPSCAN = 1 << 1,
	SO_TYPE_SAMPLESCAN = 1 << 2,
	SO_TYPE_TIDSCAN = 1 << 3,
	SO_TYPE_TIDRANGESCAN = 1 << 4,
	SO_TYPE_ANALYZE = 1 << 5,

	/* 可以指定多个 SO_ALLOW_* */
	/* 允许或不允许使用访问策略 */
	SO_ALLOW_STRAT = 1 << 6,
	/* 是否报告位置以用于 syncscan 逻辑？ */
	SO_ALLOW_SYNC = 1 << 7,
	/* 一次验证一页的可见性？ */
	SO_ALLOW_PAGEMODE = 1 << 8,

	/* 在扫描结束时注销快照？ */
	SO_TEMP_SNAPSHOT = 1 << 9
} ScanOptions;

/*
 * 表_{更新,删除,锁定元组}的结果代码，以及表 AM 中的可见性
 * 例程的结果代码。
 */
typedef enum TM_Result
{
	/*
	 * 表示动作成功（即已执行更新/删除，锁已获取）
	 */
	TM_Ok,

	/* 受影响的元组对相关快照不可见 */
	TM_Invisible,

	/* 受影响的元组已被调用的后端修改 */
	TM_SelfModified,

	/*
	 * 受影响的元组已被另一个事务更新。这包括元组被移动到另一个分区的情况。
	 */
	TM_Updated,

	/* 受影响的元组已被另一个事务删除 */
	TM_Deleted,

	/*
	 * 当前另一个会话正在修改受影响的元组。只有当 table_(update/delete/lock_tuple)
	 * 被指示不等待时，才会返回此。
	 */
	TM_BeingModified,

	/* 无法获取锁，跳过动作。仅由 lock_tuple 使用 */
	TM_WouldBlock
} TM_Result;

/*
 * 当 table_tuple_update、table_tuple_delete 或 table_tuple_lock 失败
 * 因为目标元组已经过期时，它们会填充这个结构，以提供关于发生了什么的
 * 信息给调用者。
 *
 * ctid 是目标的 ctid 链接：如果目标已删除，则与目标的 TID 相同，
 * 或者如果目标已更新，则是替换元组的位置。
 *
 * xmax 是过期事务的 XID。如果调用者想要访问替换元组，它必须在相信
 * 替换确实匹配之前检查此项是否匹配。如果目标不是 !LP_NORMAL，
 * 则这将是 InvalidTransactionId（仅期望从 syscache 检索 TID 时）。
 *
 * cmax 是过期命令的 CID，但仅当失败代码为 TM_SelfModified 时
 *（即当前事务中的某些内容使元组过期）；否则 cmax 为零。
 * （我们做此限制，因为 HeapTupleHeaderGetCmax 不适用于在其他事务中过期的元组。）
 */
typedef struct TM_FailureData
{
	ItemPointerData ctid;
	TransactionId xmax;
	CommandId	cmax;
	bool		traversed;
} TM_FailureData;


typedef struct TM_IndexDelete
{
	ItemPointerData tid;		/* table TID from index tuple */
	int16		id;				/* TM_IndexStatus 数组中的偏移量 */
} TM_IndexDelete;

typedef struct TM_IndexStatus
{
	OffsetNumber idxoffnum;		/* 索引 AM 页偏移号 */
	bool		knowndeletable; /* 当前已知可以删除？ */

	/* 自下而上的索引删除特定字段如下 */
	bool		promising;		/* 有前景的（重复）索引元组？ */
	int16		freespace;		/* 如果被删除，索引中释放的空间 */
} TM_IndexStatus;

/*
 * 索引 AM/tableam 协调是自下而上索引删除设计的核心。
 * 索引 AM 通过将某些条目标记为“有前景”来提供关于查找位置
 * 的提示。索引 AM 通过怀疑是由于未逻辑修改已索引值的 UPDATEs 
 * 而留下的旧版本的重复索引元组来实现这一点。索引 AM
 * 可能发现仅在最近被认为受到这种 UPDATE 影响时才将条目标记为有前景更有帮助。
 *
 * 自下而上的索引删除最初的范围较广，通常会包括目标索引页面
 * 上的所有 TIDs。由 tableam 负责检查事务状态信息的成本。
 * tableam 处于控制之中，但需要索引 AM 的仔细指导。索引 AM 请求
 * 满足 bottomupfreespace 目标，而 tableam 通过统计已知可删除条目的
 * 每项可用空间值来测量向该目标的进度。（所有 !bottomup 调用者
 * 都可以将这些空间相关字段设置为零。）
 */
typedef struct TM_IndexDeleteOp
{
	Relation	irel;			/* 目标索引关系 */
	BlockNumber iblknum;		/* 索引块编号（用于错误报告） */
	bool		bottomup;		/* 自下而上的（而非简单）删除？ */
	int			bottomupfreespace;	/* 自下而上的空间目标 */

	/* 可变的每个 TID 信息如下（索引 AM 初始化条目） */
	int			ndeltids;		/* 当前 deltids/status 元素的数量 */
	TM_IndexDelete *deltids;
	TM_IndexStatus *status;
} TM_IndexDeleteOp;

/* table_tuple_insert 的“选项”标志位 */
/* TABLE_INSERT_SKIP_WAL 是 0x0001；RelationNeedsWAL() 现在统治 */
#define TABLE_INSERT_SKIP_FSM		0x0002
#define TABLE_INSERT_FROZEN			0x0004
#define TABLE_INSERT_NO_LOGICAL		0x0008

/* 表格元组锁的标志位 */
/* 跟踪更新正在进行的元组，如果锁模式没有冲突 */
#define TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS	(1 << 0)
/* 跟踪更新链并锁定元组的最新版本 */
#define TUPLE_LOCK_FLAG_FIND_LAST_VERSION		(1 << 1)


/* 表格索引构建扫描的回调函数的类型定义 */
typedef void (*IndexBuildCallback) (Relation index,
									ItemPointer tid,
									Datum *values,
									bool *isnull,
									bool tupleIsAlive,
									void *state);

/*
 * 表格 AM 的 API 结构。请注意，这必须以
 * 服务器生命周期的方式分配，通常作为一个静态的常量结构，
 * 然后由 FormData_pg_am.amhandler 返回。
 *
 * 在大多数情况下，不适合直接调用回调，应该使用
 * table_* 包装函数。
 *
 * GetTableAmRoutine() 断言所需的回调已填充，添加回调时
 * 别忘了更新。
 */
typedef struct TableAmRoutine
{
	/* 这必须设置为 T_TableAmRoutine */
	NodeTag		type;


	/* ------------------------------------------------------------------------
	 * 与插槽相关的回调。
	 * ------------------------------------------------------------------------
	 */

	/*
	 * 返回适合存储此 AM 的元组的插槽实现。
	 */
	const TupleTableSlotOps *(*slot_callbacks) (Relation rel);


	/* ------------------------------------------------------------------------
	 * 表扫描回调。
	 * ------------------------------------------------------------------------
	 */

	/*
	 * 启动 `rel` 的扫描。回调必须返回 TableScanDesc，
	 * 通常会嵌入在一个更大的、与 AM 相关的结构中。
	 *
	 * 如果 nkeys != 0，则需要按这些扫描键过滤结果。
	 *
	 * 如果 pscan 不为 NULL，则它已经通过
	 * parallelscan_initialize() 初始化，并且必须针对同一关系。
	 * 只会在来自 table_beginscan_parallel() 时设置。
	 *
	 * `flags` 是一个位掩码，指示扫描的类型（ScanOptions 的
	 * SO_TYPE_*，目前只能指定一个），控制扫描行为的选项
	 * （ScanOptions 的 SO_ALLOW_*，可以指定多个，AM 可能会忽略不支持的选项）
	 * 和快照是否需要在 scan_end 时被解除分配（ScanOptions 的 
	 * SO_TEMP_SNAPSHOT）。
	 */
	TableScanDesc (*scan_begin) (Relation rel,
								 Snapshot snapshot,
								 int nkeys, struct ScanKeyData *key,
								 ParallelTableScanDesc pscan,
								 uint32 flags);

	/*
	 * 释放资源并解除扫描分配。如果 TableScanDesc.temp_snap，
	 * TableScanDesc.rs_snapshot 需要被注销。
	 */
	void		(*scan_end) (TableScanDesc scan);

	/*
	 * 重新启动关系扫描。如果 set_params 设置为 true，允许_{strat，
	 * sync, pagemode}（见 scan_begin）所做的更改应予以考虑。
	 */
	void		(*scan_rescan) (TableScanDesc scan, struct ScanKeyData *key,
								bool set_params, bool allow_strat,
								bool allow_sync, bool allow_pagemode);

	/*
	 * 从 `scan` 返回下一个元组，存储在槽中。
	 */
	bool		(*scan_getnextslot) (TableScanDesc scan,
									 ScanDirection direction,
									 TupleTableSlot *slot);

	/*-----------
	 * 提供对 ItemPointers 范围的扫描的可选函数。
	 * 实现必须提供这两个函数中的一个，或者都不提供。
	 *
	 * scan_set_tidrange 的实现必须自己处理
	 * 任何值的 ItemPointers。即，它们必须处理以下每一项：
	 *
	 * 1) mintid 或 maxtid 超出表的结束；以及
	 * 2) mintid 高于 maxtid；以及
	 * 3) mintid 或 maxtid 的项偏移超出 AM 允许的最大偏移量。
	 *
	 * 实现可以假设 scan_set_tidrange 总是在 can_getnextslot_tidrange
	 * 之前调用，或者在 scan_rescan 之后和任何进一步的
	 * scan_getnextslot_tidrange 调用之前。
	 */
	void		(*scan_set_tidrange) (TableScanDesc scan,
									  ItemPointer mintid,
									  ItemPointer maxtid);

	/*
	 * 返回 `scan` 中的下一个元组，该元组在由
	 * scan_set_tidrange 定义的 TIDs 范围内。
	 */
	bool		(*scan_getnextslot_tidrange) (TableScanDesc scan,
											  ScanDirection direction,
											  TupleTableSlot *slot);

	/* ------------------------------------------------------------------------
	 * 并行表扫描相关函数。
	 * ------------------------------------------------------------------------
	 */

	/*
	 * 估计对该关系进行并行扫描所需的共享内存大小。
	 * 快照不需要计入。
	 */
	Size		(*parallelscan_estimate) (Relation rel);

	/*
	 * 初始化 ParallelTableScanDesc，以进行该关系的并行扫描。
	 * `pscan` 将根据 parallelscan_estimate() 为同一关系进行调整。
	 */
	Size		(*parallelscan_initialize) (Relation rel,
											ParallelTableScanDesc pscan);

	/*
	 * 重新初始化 `pscan` 以进行新的扫描。`rel` 将与
	 * `pscan` 由 parallelscan_initialize 初始化时相同。
	 */
	void		(*parallelscan_reinitialize) (Relation rel,
											  ParallelTableScanDesc pscan);


	/* ------------------------------------------------------------------------
	 * 索引扫描回调
	 * ------------------------------------------------------------------------
	 */

	/*
	 * 准备从关系中获取元组，在进行索引扫描时需要。
	 * 回调必须返回一个 IndexFetchTableData，
	 * AM 通常会将其嵌入一个更大的结构中，包含额外的信息。
	 *
	 * 然后，可以通过 index_fetch_tuple 获取索引扫描的元组。
	 */
	struct IndexFetchTableData *(*index_fetch_begin) (Relation rel);

	
/*
	 * 重置索引获取。通常这将释放在IndexFetchTableData中持有的跨索引获取资源。
	 */
	void		(*index_fetch_reset) (struct IndexFetchTableData *data);

	/*
	 * 释放资源并解除索引获取的分配。
	 */
	void		(*index_fetch_end) (struct IndexFetchTableData *data);

	/*
	 * 在进行根据`snapshot`的可见性测试后，将`tid`处的元组获取到`slot`中。如果找到一个元组并通过了可见性测试，则返回true，否则返回false。
	 *
	 * 注意，AM在索引列没有变化时不一定会更新索引，需要返回对快照可见的元组的当前/正确版本，即使tid指向的是元组的旧版本。
	 *
	 * *call_again在对tid第一次调用index_fetch_tuple时为false。
	 * 如果可能存在另一个与tid匹配的元组，*call_again需要由index_fetch_tuple设置为true，向调用者发出信号，表示应该对相同的tid再次调用index_fetch_tuple。
	 *
	 * 如果all_dead不为NULL，index_fetch_tuple应在保证没有后端需要查看该元组的情况下将其设置为true。索引AM可以用此避免在未来的搜索中返回该tid。
	 */
	bool		(*index_fetch_tuple) (struct IndexFetchTableData *scan,
									  ItemPointer tid,
									  Snapshot snapshot,
									  TupleTableSlot *slot,
									  bool *call_again, bool *all_dead);


	/* ------------------------------------------------------------------------
	 * 针对单个元组的非修改操作的回调
	 * ------------------------------------------------------------------------
	 */

	/*
	 * 在进行根据`snapshot`的可见性测试后，将`tid`处的元组获取到`slot`中。如果找到一个元组并通过了可见性测试，则返回true，否则返回false。
	 */
	bool		(*tuple_fetch_row_version) (Relation rel,
											ItemPointer tid,
											Snapshot snapshot,
											TupleTableSlot *slot);

	/*
	 * tid 是否有效，可以用于扫描此关系。
	 */
	bool		(*tuple_tid_valid) (TableScanDesc scan,
									ItemPointer tid);

	/*
	 * 通过更新 `tid` 指向最新版本，返回 `tid` 处元组的最新版本。
	 */
	void		(*tuple_get_latest_tid) (TableScanDesc scan,
										 ItemPointer tid);

	/*
	 * `slot` 中的元组是否满足 `snapshot`？该 slot 需要是
	 * 适合 AM 的类型。
	 */
	bool		(*tuple_satisfies_snapshot) (Relation rel,
											 TupleTableSlot *slot,
											 Snapshot snapshot);

	/* 查看 table_index_delete_tuples() */
	TransactionId (*index_delete_tuples) (Relation rel,
										  TM_IndexDeleteOp *delstate);


	/* ------------------------------------------------------------------------
	 * 物理元组的操作。
	 * ------------------------------------------------------------------------
	 */

	/* 查看 table_tuple_insert() 以获取参数参考 */
	void		(*tuple_insert) (Relation rel, TupleTableSlot *slot,
								 CommandId cid, int options,
								 struct BulkInsertStateData *bistate);

	/* 查看 table_tuple_insert_speculative() 以获取参数参考 */
	void		(*tuple_insert_speculative) (Relation rel,
											 TupleTableSlot *slot,
											 CommandId cid,
											 int options,
											 struct BulkInsertStateData *bistate,
											 uint32 specToken);

	/* 查看 table_tuple_complete_speculative() 以获取参数参考 */
	void		(*tuple_complete_speculative) (Relation rel,
											   TupleTableSlot *slot,
											   uint32 specToken,
											   bool succeeded);

	/* 查看 table_multi_insert() 以获取参数参考 */
	void		(*multi_insert) (Relation rel, TupleTableSlot **slots, int nslots,
								 CommandId cid, int options, struct BulkInsertStateData *bistate);

	/* 查看 table_tuple_delete() 以获取参数参考 */
	TM_Result	(*tuple_delete) (Relation rel,
								 ItemPointer tid,
								 CommandId cid,
								 Snapshot snapshot,
								 Snapshot crosscheck,
								 bool wait,
								 TM_FailureData *tmfd,
								 bool changingPart);

	/* 查看 table_tuple_update() 以获取参数参考 */
	TM_Result	(*tuple_update) (Relation rel,
								 ItemPointer otid,
								 TupleTableSlot *slot,
								 CommandId cid,
								 Snapshot snapshot,
								 Snapshot crosscheck,
								 bool wait,
								 TM_FailureData *tmfd,
								 LockTupleMode *lockmode,
								 bool *update_indexes);

	/* 查看 table_tuple_lock() 以获取参数参考 */
	TM_Result	(*tuple_lock) (Relation rel,
							   ItemPointer tid,
							   Snapshot snapshot,
							   TupleTableSlot *slot,
							   CommandId cid,
							   LockTupleMode mode,
							   LockWaitPolicy wait_policy,
							   uint8 flags,
							   TM_FailureData *tmfd);

	/*
	 * 执行必要的操作以完成通过
	 * tuple_insert 和 multi_insert 进行的插入，指定 BulkInsertState。树状中的
	 * 访问方法不再使用此功能。
	 *
	 * 通常，tuple_insert 和 multi_insert 的调用者只会传递所有适用的
	 * 标志，每个 AM 必须决定哪些标志对其有意义，然后仅对
	 * finish_bulk_insert 中的这些标志采取行动，并忽略其他标志。
	 *
	 * 可选回调。
	 */
	void		(*finish_bulk_insert) (Relation rel, int options);


	/* ------------------------------------------------------------------------
	 * DDL 相关功能。
	 * ------------------------------------------------------------------------
	 */

	/*
	 * 该回调需要为 `rel` 创建一个新的关系文件节点，
	 * 并具有适当的持久性行为以满足 `persistence`。
	 *
	 * 请注意，仅能依赖于 RelationBuildLocalRelation() 填充的
	 * relcache 的子集，且关系的目录条目要么尚不存在（新关系），
	 * 要么仍然引用旧的 relfilenode。
	 *
	 * 输出 *freezeXid, *minmulti 必须设置为适当的值
	 * pg_class.{relfrozenxid, relminmxid}。对于不需要填充这些
	 * 字段的 AM，它们可以分别设置为 InvalidTransactionId 和
	 * InvalidMultiXactId。
	 *
	 * 另见 table_relation_set_new_filenode()。
	 */
	void		(*relation_set_new_filenode) (Relation rel,
											  const RelFileNode *newrnode,
											  char persistence,
											  TransactionId *freezeXid,
											  MultiXactId *minmulti);

	/*
	 * 该回调需要从 `rel` 的当前
	 * relfilenode 中删除所有内容。无需考虑事务行为。
	 * 通常可以通过将底层存储截断为最小大小来实现。
	 *
	 * 另见 table_relation_nontransactional_truncate()。
	 */
	void		(*relation_nontransactional_truncate) (Relation rel);

	/*
	 * 查看 table_relation_copy_data()。
	 *
	 * 这通常可以通过直接复制底层
	 * 存储来实现，除非它在内部包含对表空间的引用。
	 */
	void		(*relation_copy_data) (Relation rel,
									   const RelFileNode *newrnode);

	/* 查看 table_relation_copy_for_cluster() */
	void		(*relation_copy_for_cluster) (Relation OldTable,
											  Relation NewTable,
											  Relation OldIndex,
											  bool use_sort,
											  TransactionId OldestXmin,
											  TransactionId *xid_cutoff,
											  MultiXactId *multi_cutoff,
											  double *num_tuples,
											  double *tups_vacuumed,
											  double *tups_recently_dead);

	/*
	 * 响应关系上的 VACUUM 命令。VACUUM 可以由
	 * 用户触发或由自动真空触发。AM 执行的特定操作将
	 * 在很大程度上依赖于个别 AM。
	 *
	 * 进入时，事务已建立，并且关系
	 * 被 ShareUpdateExclusive 锁定。
	 *
	 * 请注意，VACUUM FULL（和 CLUSTER）以及 ANALYZE
	 * 都不会通过此例程，即使（对于 ANALYZE）它也是同一 VACUUM
	 * 命令的一部分。
	 *
	 * 未来可能需要有一个单独的回调来
	 * 与自动真空的调度进行集成。
	 */
	void		(*relation_vacuum) (Relation rel,
									struct VacuumParams *params,
									BufferAccessStrategy bstrategy);

	/*
	 * 准备分析 `scan` 的块 `blockno`。扫描已通过
	 * table_beginscan_analyze() 开始。另见
	 * table_scan_analyze_next_block()。
	 *
	 * 回调可能会获取在 table_scan_analyze_next_tuple() 返回 false
	 * 之前持有的资源，例如锁。直到块中的所有元组被
	 * scan_analyze_next_tuple 分析完毕，持有锁是有意义的。
	 *
	 * 如果块不适合采样，例如，因为它是一个永远不会
	 * 包含元组的元数据页面，回调可以返回 false。
	 *
	 * XXX：这显然主要适用于基于块的 AM。目前尚不清楚
	 * 对于非基于块的 AM 来说良好的接口是什么，因此尚无。
	 */
	bool		(*scan_analyze_next_block) (TableScanDesc scan,
											BlockNumber blockno,
											BufferAccessStrategy bstrategy);

	/*
	 * 查看 table_scan_analyze_next_tuple()。
	 *
	 * 不是每个 AM 都可能具有有意义的死亡行概念，
	 * 在这种情况下，不增加 *deadrows 是可以的，
	 * 但请注意，这可能会影响自动真空调度（参见 relation_vacuum
	 * 回调的注释）。
	 */
	bool		(*scan_analyze_next_tuple) (TableScanDesc scan,
											TransactionId OldestXmin,
											double *liverows,
											double *deadrows,
											TupleTableSlot *slot);

	/* 查看 table_index_build_range_scan 以获取参数参考 */
	double		(*index_build_range_scan) (Relation table_rel,
										   Relation index_rel,
										   struct IndexInfo *index_info,
										   bool allow_sync,
										   bool anyvisible,
										   bool progress,
										   BlockNumber start_blockno,
										   BlockNumber numblocks,
										   IndexBuildCallback callback,
										   void *callback_state,
										   TableScanDesc scan);

	/* 查看 table_index_validate_scan 以获取参数参考 */
	void		(*index_validate_scan) (Relation table_rel,
										Relation index_rel,
										struct IndexInfo *index_info,
										Snapshot snapshot,
										struct ValidateIndexState *state);


	
/* ------------------------------------------------------------------------
	 * 杂项函数。
	 * ------------------------------------------------------------------------
	 */

	/*
	 * 请参见 table_relation_size()。
	 *
	 * 请注意，目前有几个调用者使用 MAIN_FORKNUM 大小来确定
	 * 可能感兴趣的块的范围（brin，分析）。我们很可能需要在某个
	 * 时候修订这些接口。
	 */
	uint64		(*relation_size) (Relation rel, ForkNumber forkNumber);


	/*
	 * 如果关系需要 TOAST 表，则此回调应返回 true；如果不需要，则返回 false。
	 * 它可能希望在做出决定之前检查关系的元组描述符，但如果它使用其他存储
	 * 大值的方法（或不支持这些方法），则可以简单地返回 false。
	 */
	bool		(*relation_needs_toast_table) (Relation rel);

	/*
	 * 此回调应返回实现 TOAST 表的表 AM 的 OID。
	 * 如果 relation_needs_toast_table 回调始终返回 false，则此回调不是必需的。
	 */
	Oid			(*relation_toast_am) (Relation rel);

	/*
	 * 当从此 AM 实现的 toast 表中解除吐值时，会调用此回调。
	 * 有关更多详情，请参见 table_relation_fetch_toast_slice()。
	 */
	void		(*relation_fetch_toast_slice) (Relation toastrel, Oid valueid,
											   int32 attrsize,
											   int32 sliceoffset,
											   int32 slicelength,
											   struct varlena *result);


	/* ------------------------------------------------------------------------
	 * 规划器相关函数。
	 * ------------------------------------------------------------------------
	 */

	/*
	 * 查看 table_relation_estimate_size()。
	 *
	 * 虽然是基于块的，但对一个内部不使用块的 AM 来说，将其转换为可用的表示不应该太难。
	 *
	 * 这与 relation_size 回调不同，它返回大小估算（包括关系大小和元组计数）用于规划目的，而不是返回当前的正确估算。
	 */
	void		(*relation_estimate_size) (Relation rel, int32 *attr_widths,
										   BlockNumber *pages, double *tuples,
										   double *allvisfrac);


	/* ------------------------------------------------------------------------
	 * 执行器相关函数。
	 * ------------------------------------------------------------------------
	 */

	/*
	 * 准备从 `tbmres->blockno` 获取/检查/返回元组作为位图表扫描的一部分。`scan` 是通过 table_beginscan_bm() 启动的。
	 * 如果页面上没有元组可供查找，则返回 false，否则返回 true。
	 *
	 * 通常这将读取并固定目标块，并进行必要的工作以允许 scan_bitmap_next_tuple() 返回元组（例如，此时进行元组可见性检查可能是有意义的）。对于某些 AM，在这里引用 `tbmres` 内容进行所有工作会更有意义，而对于其他 AM，可能更好将更多工作推迟到 scan_bitmap_next_tuple。
	 *
	 * 如果 `tbmres->blockno` 为 -1，则这是一个有损扫描，必须返回页面上所有可见的元组，否则需要返回 `tbmres->offsets` 中的偏移位置的元组。
	 *
	 * XXX：目前只有当 AM 使用 md.c 作为其存储管理器，并以将 blockids 直接映射到基础存储的方式使用 ItemPointer->ip_blkid 时，才可以实现这一点。nodeBitmapHeapscan.c 直接使用该接口进行预取。这可能需要在稍后的时间 rectified。
	 *
	 * XXX：目前只有当 AM 使用可见性映射时，nodeBitmapHeapscan.c 才无条件访问它以执行预取。这可能需要在稍后的时间 rectified。
	 *
	 * 可选回调，但 scan_bitmap_next_block 和 scan_bitmap_next_tuple 必须都存在，或者都不存在。
	 */
	bool		(*scan_bitmap_next_block) (TableScanDesc scan,
										   struct TBMIterateResult *tbmres);

	/*
	 * 将位图表扫描的下一个元组提取到 `slot` 中，如果找到可见元组，则返回 true，否则返回 false。
	 *
	 * 对于某些 AM，在 scan_bitmap_next_block 中引用 `tbmres` 内容的所有工作会更有意义，而对于其他 AM，可能更好将更多工作推迟到此回调。
	 *
	 * 可选回调，但 scan_bitmap_next_block 和 scan_bitmap_next_tuple 必须都存在，或者都不存在。
	 */
	bool		(*scan_bitmap_next_tuple) (TableScanDesc scan,
										   struct TBMIterateResult *tbmres,
										   TupleTableSlot *slot);

	/*
	 * 准备从采样扫描中的下一个块提取元组。如果采样扫描已完成，则返回 false，否则返回 true。`scan` 是通过 table_beginscan_sampling() 启动的。
	 *
	 * 通常这会首先通过调用 TsmRoutine 的 NextSampleBlock() 回调（如果不为 NULL）来确定目标块，或替代性地对所有块执行顺序扫描。然后通常读取并固定所确定的块。
	 *
	 * 由于 TsmRoutine 接口是基于块的，因此需要将块传递给 NextSampleBlock()。如果这对某个 AM 不合适，则它需要在内部执行内部表示与基于块的表示之间的映射。
	 *
	 * 请注意，在 scan_sample_next_tuple() 已经耗尽块上的元组之前，不接受持有可能造成死锁的资源，例如 lwlocks——元组可能会返回给上层查询节点，并且下一个调用可能会相隔很长时间。持有缓冲区固定等显然是可以的。
	 *
	 * 目前必须实现此接口，因为没有其他替代方法（与位图扫描相对）来实现采样扫描。如果不可行，AM 可以引发错误。
	 */
	bool		(*scan_sample_next_block) (TableScanDesc scan,
										   struct SampleScanState *scanstate);

	/*
	 * 此回调仅在 scan_sample_next_block 返回 true 之后被调用，应使用 TsmRoutine 的 NextSampleTuple() 回调确定要从选定块返回的下一个元组。
	 *
	 * 此回调需要执行可见性检查，并仅返回可见元组。这显然可能意味着多次调用 NextSampleTuple()。
	 *
	 * TsmRoutine 接口假定给定页面上存在最大偏移量，因此，如果这不适用于 AM，它需要以某种方式模拟该假设。
	 */
	bool		(*scan_sample_next_tuple) (TableScanDesc scan,
										   struct SampleScanState *scanstate,
										   TupleTableSlot *slot);

} TableAmRoutine;


/* ----------------------------------------------------------------------------
 * 槽函数。
 * ----------------------------------------------------------------------------
 */

/*
 * 返回适合持有关系的适当类型元组的槽回调。适用于表、视图、外部表和分区表。
 */
extern const TupleTableSlotOps *table_slot_callbacks(Relation rel);

/*
 * 使用 table_slot_callbacks() 返回的回调返回槽，并将其注册在 *reglist 上。
 */
extern TupleTableSlot *table_slot_create(Relation rel, List **reglist);


/* ----------------------------------------------------------------------------
 * 表扫描函数。
 * ----------------------------------------------------------------------------
 */

/*
 * 开始对 `rel` 的扫描。返回的元组通过了 `snapshot` 的可见性测试，并且如果 nkeys != 0，则结果会被这些扫描键过滤。
 */
static inline TableScanDesc
table_beginscan(Relation rel, Snapshot snapshot,
				int nkeys, struct ScanKeyData *key)
{
	uint32		flags = SO_TYPE_SEQSCAN |
	SO_ALLOW_STRAT | SO_ALLOW_SYNC | SO_ALLOW_PAGEMODE;

	return rel->rd_tableam->scan_begin(rel, snapshot, nkeys, key, NULL, flags);
}


/*
 * 像 table_beginscan() 一样，但用于扫描目录。它会自动使用适合扫描目录关系的快照。
 */
extern TableScanDesc table_beginscan_catalog(Relation rel, int nkeys,
											 struct ScanKeyData *key);

/*
 * 像 table_beginscan() 一样，但 table_beginscan_strat() 提供了一个扩展的 API
 * 允许调用方控制是否可以使用非默认的缓冲区访问策略，
 * 以及是否可以选择 syncscan（这可能导致扫描不从块零开始）。这两者在
 * plain table_beginscan 中默认都为 true。
 */
static inline TableScanDesc
table_beginscan_strat(Relation rel, Snapshot snapshot,
					  int nkeys, struct ScanKeyData *key,
					  bool allow_strat, bool allow_sync)
{
	uint32		flags = SO_TYPE_SEQSCAN | SO_ALLOW_PAGEMODE;

	if (allow_strat)
		flags |= SO_ALLOW_STRAT;
	if (allow_sync)
		flags |= SO_ALLOW_SYNC;

	return rel->rd_tableam->scan_begin(rel, snapshot, nkeys, key, NULL, flags);
}

/*
 * table_beginscan_bm 是为位图堆扫描设置 TableScanDesc 的替代入口点。
 * 尽管该扫描技术实际上与标准的 seqscan 非常不同，但有足够的共性
 * 使得使用相同的数据结构变得有意义。
 */
static inline TableScanDesc
table_beginscan_bm(Relation rel, Snapshot snapshot,
				   int nkeys, struct ScanKeyData *key)
{
	uint32		flags = SO_TYPE_BITMAPSCAN | SO_ALLOW_PAGEMODE;

	return rel->rd_tableam->scan_begin(rel, snapshot, nkeys, key, NULL, flags);
}

/*
 * table_beginscan_sampling 是为 TABLESAMPLE 扫描设置 TableScanDesc 的替代入口点。
 * 与位图扫描一样，尽管行为相当不同，但使用相同的数据结构是值得的。
 * 除了 table_beginscan_strat 提供的选项外，此调用还允许控制是否使用
 * 页面模式可见性检查。
 */
static inline TableScanDesc
table_beginscan_sampling(Relation rel, Snapshot snapshot,
						 int nkeys, struct ScanKeyData *key,
						 bool allow_strat, bool allow_sync,
						 bool allow_pagemode)
{
	uint32		flags = SO_TYPE_SAMPLESCAN;

	if (allow_strat)
		flags |= SO_ALLOW_STRAT;
	if (allow_sync)
		flags |= SO_ALLOW_SYNC;
	if (allow_pagemode)
		flags |= SO_ALLOW_PAGEMODE;

	return rel->rd_tableam->scan_begin(rel, snapshot, nkeys, key, NULL, flags);
}

/*
 * table_beginscan_tid 是为 Tid 扫描设置 TableScanDesc 的替代入口点。
 * 与位图扫描一样，尽管行为相当不同，但使用相同的数据结构是值得的。
 */
static inline TableScanDesc
table_beginscan_tid(Relation rel, Snapshot snapshot)
{
	uint32		flags = SO_TYPE_TIDSCAN;

	return rel->rd_tableam->scan_begin(rel, snapshot, 0, NULL, NULL, flags);
}

/*
 * table_beginscan_analyze 是为 ANALYZE 扫描设置 TableScanDesc 的替代入口点。
 * 与位图扫描一样，尽管行为相当不同，但使用相同的数据结构是值得的。
 */
static inline TableScanDesc
table_beginscan_analyze(Relation rel)
{
	uint32		flags = SO_TYPE_ANALYZE;

	return rel->rd_tableam->scan_begin(rel, NULL, 0, NULL, NULL, flags);
}

/*
 * 结束关系扫描。
 */
static inline void
table_endscan(TableScanDesc scan)
{
	scan->rs_rd->rd_tableam->scan_end(scan);
}

/*
 * 重启关系扫描。
 */
static inline void
table_rescan(TableScanDesc scan,
			 struct ScanKeyData *key)
{
	scan->rs_rd->rd_tableam->scan_rescan(scan, key, false, false, false, false);
}

/*
 * 在更改参数后重启关系扫描。
 *
 * 此调用允许在开始新的扫描之前更改缓冲区策略、syncscan 和页面模式
 * 选项。请注意，尽管 syncscan 的实际使用可能会改变（实际上，启用或禁用报告），
 * 但之前选择的 startblock 将被保留。
 */
static inline void
table_rescan_set_params(TableScanDesc scan, struct ScanKeyData *key,
						bool allow_strat, bool allow_sync, bool allow_pagemode)
{
	scan->rs_rd->rd_tableam->scan_rescan(scan, key, true,
										 allow_strat, allow_sync,
										 allow_pagemode);
}

/*
 * 更新扫描中使用的快照。
 */
extern void table_scan_update_snapshot(TableScanDesc scan, Snapshot snapshot);

/*
 * 从 `scan` 返回下一元组，存储在槽中。
 */
static inline bool
table_scan_getnextslot(TableScanDesc sscan, ScanDirection direction, TupleTableSlot *slot)
{
	slot->tts_tableOid = RelationGetRelid(sscan->rs_rd);

	/*
	 * 我们不期望会直接调用带有效的 CheckXidAlive 的 table_scan_getnextslot
	 * 来处理目录或常规表。请参阅 xact.c 中声明这些变量的详细注释。
	 */
	if (unlikely(TransactionIdIsValid(CheckXidAlive) && !bsysscan))
		elog(ERROR, "unexpected table_scan_getnextslot call during logical decoding");

	return sscan->rs_rd->rd_tableam->scan_getnextslot(sscan, direction, slot);
}

/* ----------------------------------------------------------------------------
 * TID 范围扫描相关函数。
 * ----------------------------------------------------------------------------
 */

/*
 * table_beginscan_tidrange 是为 TID 范围扫描设置 TableScanDesc 的入口点。
 */
static inline TableScanDesc
table_beginscan_tidrange(Relation rel, Snapshot snapshot,
						 ItemPointer mintid,
						 ItemPointer maxtid)
{
	TableScanDesc sscan;
	uint32		flags = SO_TYPE_TIDRANGESCAN | SO_ALLOW_PAGEMODE;

	sscan = rel->rd_tableam->scan_begin(rel, snapshot, 0, NULL, NULL, flags);

	/* 设置要扫描的 TID 范围 */
	sscan->rs_rd->rd_tableam->scan_set_tidrange(sscan, mintid, maxtid);

	return sscan;
}

/*
 * table_rescan_tidrange 重置扫描位置，并设置要为通过
 * table_beginscan_tidrange 创建的 TableScanDesc 扫描的最小和最大 TID 范围。
 */
static inline void
table_rescan_tidrange(TableScanDesc sscan, ItemPointer mintid,
					  ItemPointer maxtid)
{
	/* 确保使用了 table_beginscan_tidrange()。 */
	Assert((sscan->rs_flags & SO_TYPE_TIDRANGESCAN) != 0);

	sscan->rs_rd->rd_tableam->scan_rescan(sscan, NULL, false, false, false, false);
	sscan->rs_rd->rd_tableam->scan_set_tidrange(sscan, mintid, maxtid);
}

/*
 * 从为 table_beginscan_tidrange() 创建的 TID 范围扫描中从 `sscan` 获取下一个元组。
 * 将元组存储在 `slot` 中并返回 true，如果范围内不存在更多元组则返回 false。
 */
static inline bool
table_scan_getnextslot_tidrange(TableScanDesc sscan, ScanDirection direction,
								TupleTableSlot *slot)
{
	/* 确保使用了 table_beginscan_tidrange()。 */
	Assert((sscan->rs_flags & SO_TYPE_TIDRANGESCAN) != 0);

	return sscan->rs_rd->rd_tableam->scan_getnextslot_tidrange(sscan,
															   direction,
															   slot);
}


/* ----------------------------------------------------------------------------
 * 并行表扫描相关函数。
 * ----------------------------------------------------------------------------
 */

/*
 * 估算对该关系进行并行扫描所需的共享内存大小。
 */
extern Size table_parallelscan_estimate(Relation rel, Snapshot snapshot);

/*
 * 初始化 ParallelTableScanDesc 以对该关系进行并行扫描。
 * `pscan` 的大小需要根据 parallelscan_estimate() 的结果进行设置，
 * 该函数针对同一关系调用；在领导进程中仅调用一次，然后，
 * 各个工作进程通过 table_beginscan_parallel 进行附加。
 */
extern void table_parallelscan_initialize(Relation rel,
										  ParallelTableScanDesc pscan,
										  Snapshot snapshot);

/*
 * 开始一个并行扫描。`pscan` 需要通过
 * table_parallelscan_initialize() 初始化，用于相同的关系。初始化
 * 不需要在这个后端中发生。
 *
 * 调用者必须对关系持有适当的锁。
 */
extern TableScanDesc table_beginscan_parallel(Relation rel,
											  ParallelTableScanDesc pscan);

/*
 * 重新启动一个并行扫描。 在领导进程中调用此函数。调用者
 * 负责确保所有工作线程都已完成扫描。
 */
static inline void
table_parallelscan_reinitialize(Relation rel, ParallelTableScanDesc pscan)
{
	rel->rd_tableam->parallelscan_reinitialize(rel, pscan);
}


/* ----------------------------------------------------------------------------
 *  与索引扫描相关的函数。
 * ----------------------------------------------------------------------------
 */

/*
 * 准备从关系中提取元组，当需要提取索引扫描的元组时。
 *
 * 索引扫描的元组可以通过 table_index_fetch_tuple() 来提取。
 */
static inline IndexFetchTableData *
table_index_fetch_begin(Relation rel)
{
	return rel->rd_tableam->index_fetch_begin(rel);
}

/*
 * 重置索引提取。通常这将释放在 IndexFetchTableData 中持有的
 * 跨索引提取资源。
 */
static inline void
table_index_fetch_reset(struct IndexFetchTableData *scan)
{
	scan->rel->rd_tableam->index_fetch_reset(scan);
}

/*
 * 释放资源并解除分配索引提取。
 */
static inline void
table_index_fetch_end(struct IndexFetchTableData *scan)
{
	scan->rel->rd_tableam->index_fetch_end(scan);
}

/*
 * 作为索引扫描的一部分，将位于 `tid` 的元组提取到 `slot` 中，
 * 并根据 `snapshot` 进行可见性测试。如果找到了元组并通过了
 * 可见性测试，则返回 true，否则返回 false。注意，当我们返回 true
 * 时，*tid 可能会被修改（参见后面关于通过单个索引条目可达的多个行版本的说明）。
 *
 * *call_again 在第一次调用 table_index_fetch_tuple() 时需要为
 * false。如果可能还有另一个元组与 tid 匹配，*call_again 将被设置为
 * true，表示应再次为同一 tid 调用 table_index_fetch_tuple()。
 *
 * *all_dead，如果 all_dead 不是 NULL，当且仅当保证没有后端需要看到
 * 该元组时，table_index_fetch_tuple() 将把它设置为 true。索引 AM 可以利用
 * 这一点来避免在未来的搜索中返回该 tid。
 *
 * 该函数与 table_tuple_fetch_row_version() 的区别在于，如果
 * AM 支持通过单个索引条目存储多个行版本（如堆的 HOT），则此函数返回
 * 行的当前可见版本。而 table_tuple_fetch_row_version() 仅在 `tid` 
 * 精确评估元组。在索引条目 -> 表元组查找之外，通常需要的是 
 * table_tuple_fetch_row_version()。
 */
static inline bool
table_index_fetch_tuple(struct IndexFetchTableData *scan,
						ItemPointer tid,
						Snapshot snapshot,
						TupleTableSlot *slot,
						bool *call_again, bool *all_dead)
{
	/*
	 * 我们不期望直接调用带有有效 CheckXidAlive 的 
	 * table_index_fetch_tuple 用于目录或常规表。 详细评论见
	 * xact.c 中这些变量的声明。
	 */
	if (unlikely(TransactionIdIsValid(CheckXidAlive) && !bsysscan))
		elog(ERROR, "unexpected table_index_fetch_tuple call during logical decoding");

	return scan->rel->rd_tableam->index_fetch_tuple(scan, tid, snapshot,
													slot, call_again,
													all_dead);
}

/*
 * 这是 table_index_fetch_tuple() 的一个便利封装，返回是否存在
 * 与索引条目对应的表元组项。 这可能仅对验证唯一索引中是否存在冲突有用。
 */
extern bool table_index_fetch_tuple_check(Relation rel,
										  ItemPointer tid,
										  Snapshot snapshot,
										  bool *all_dead);


/* ------------------------------------------------------------------------
 * 对单个元组进行非修改操作的函数
 * ------------------------------------------------------------------------
 */


/*
 * 在进行可见性测试后，将位于 `tid` 的元组提取到 `slot` 中，
 * 根据 `snapshot`。如果找到了元组并通过了可见性测试，则返回
 * true，否则返回 false。
 *
 * 请参阅 table_index_fetch_tuple 对这两个函数之间的区别的注释。
 * 在索引条目->表元组查找之外，使用这个函数是正确的。
 */
static inline bool
table_tuple_fetch_row_version(Relation rel,
							  ItemPointer tid,
							  Snapshot snapshot,
							  TupleTableSlot *slot)
{
	/*
	 * 我们不期望直接调用带有有效 CheckXidAlive 的 
	 * table_tuple_fetch_row_version 用于目录或常规表。  
	 * 详细评论见 xact.c 中这些变量的声明。
	 */
	if (unlikely(TransactionIdIsValid(CheckXidAlive) && !bsysscan))
		elog(ERROR, "unexpected table_tuple_fetch_row_version call during logical decoding");

	return rel->rd_tableam->tuple_fetch_row_version(rel, tid, snapshot, slot);
}

/*
 * 验证 `tid` 是否是一个潜在有效的元组标识符。这并不意味着
 * 指向的行需要存在或可见，而是尝试提取该行（例如，
 * 使用 table_tuple_get_latest_tid() 或 table_tuple_fetch_row_version()）
 * 时，如果使用该 tid 调用，则不应出错。
 *
 * `scan` 需要通过 table_beginscan() 已启动。
 */
static inline bool
table_tuple_tid_valid(TableScanDesc scan, ItemPointer tid)
{
	return scan->rs_rd->rd_tableam->tuple_tid_valid(scan, tid);
}

/*
 * 返回位于 `tid` 的元组的最新版本，更新 `tid` 以指向最新版本。
 */
extern void table_tuple_get_latest_tid(TableScanDesc scan, ItemPointer tid);

/*
 * 仅当 slot 中的元组满足快照时返回 true。
 *
 * 这假设槽中的元组是有效的，并且适合 AM 的类型。
 *
 * 一些 AM 可能会在副作用中修改元组的底层数据。如果是这样，
 * 它们应该将相关缓冲区标记为 dirty。
 */
static inline bool
table_tuple_satisfies_snapshot(Relation rel, TupleTableSlot *slot,
							   Snapshot snapshot)
{
	return rel->rd_tableam->tuple_satisfies_snapshot(rel, slot, snapshot);
}


/*
 * 根据表 TID 确定哪些索引元组可以安全删除。
 *
 * 确定索引 AM 调用者的 TM_IndexDeleteOp 状态中哪些条目指向可清理的表元组。
 * 表am发现的可清理条目自然可以安全地由索引 AM 删除，因此会被直接标记为可删除。
 * 有关详细信息，请参见 TM_IndexDelete 之上的注释和 TM_IndexDeleteOp 之上的注释。
 *
 * 返回一个 latestRemovedXid 事务 ID，调用者通常会将其放入索引删除 WAL 记录中。
 * 这可能在热备模式下的 WAL 记录后续 REDO 时使用——
 * 索引删除操作的恢复冲突可能在备用机上是必需的。
 */
static inline TransactionId
table_index_delete_tuples(Relation rel, TM_IndexDeleteOp *delstate)
{
	return rel->rd_tableam->index_delete_tuples(rel, delstate);
}


/* ----------------------------------------------------------------------------
 *  物理元组操作的函数。
 * ----------------------------------------------------------------------------
 */

/*
 * 从插槽插入一个元组到表 AM 例程中。
 *
 * 选项位掩码允许调用者指定可能改变 AM 行为的选项。 AM 会忽略其不支持的选项。
 *
 * 如果指定了 TABLE_INSERT_SKIP_FSM 选项，AM 可以自由选择不重用关系中的空闲空间。
 * 当我们知道关系是新的且不包含有用的空闲空间时，这可以节省一些周期。
 * TABLE_INSERT_SKIP_FSM 通常直接传递给 RelationGetBufferForTuple。
 * 有关更多信息，请参见该方法。
 *
 * TABLE_INSERT_FROZEN 仅应在插入到当前子事务中创建的 relfilenodes 时指定，
 * 并且在没有先前快照或现有门户打开的情况下。
 * 这会导致行被冻结，这是一种 MVCC 违规，并且需要用户选择的显式选项。
 *
 * TABLE_INSERT_NO_LOGICAL 强制禁用元组的逻辑解码信息的发出。
 * 这仅应在表重写期间使用，此时 RelationIsLogicallyLogged(relation) 在新关系上尚不准确。
 *
 * 请注意，如果元组需要任何行外数据，这些选项大多数也会在插入堆的 TOAST 表时应用。
 *
 * BulkInsertState 对象（如果有；bistate 对于默认行为可以为 NULL）也仅被传递到 RelationGetBufferForTuple。
 * 如果提供了 `bistate`，需要调用 table_finish_bulk_insert()。
 *
 * 返回时，插槽的 tts_tid 和 tts_tableOid 会被更新以反映插入。
 * 但请注意，插槽内字段的任何烤制并不反映在插槽内容中。
 */
static inline void
table_tuple_insert(Relation rel, TupleTableSlot *slot, CommandId cid,
				   int options, struct BulkInsertStateData *bistate)
{
	rel->rd_tableam->tuple_insert(rel, slot, cid, options,
								  bistate);
}

/*
 * 执行“投机插入”。这些可以在之后撤销，而无需中止整个事务。
 * 其他会话可以等待投机插入得到确认，将其转换为常规元组，或中止，就好像它从未存在过。
 * 投机插入的元组行为如同短时间的“值锁”，用于实现 INSERT .. ON CONFLICT。
 *
 * 进行投机插入的事务要么必须中止，要么完成投机插入，使用
 * table_tuple_complete_speculative(succeeded = ...)。
 */
static inline void
table_tuple_insert_speculative(Relation rel, TupleTableSlot *slot,
							   CommandId cid, int options,
							   struct BulkInsertStateData *bistate,
							   uint32 specToken)
{
	rel->rd_tableam->tuple_insert_speculative(rel, slot, cid, options,
											  bistate, specToken);
}

/*
 * 完成在同一事务中开始的“推测插入”。如果 succeeded 为 true，则元组完全插入，
 * 如果为 false，则被移除。
 */
static inline void
table_tuple_complete_speculative(Relation rel, TupleTableSlot *slot,
								 uint32 specToken, bool succeeded)
{
	rel->rd_tableam->tuple_complete_speculative(rel, slot, specToken,
												succeeded);
}

/*
 * 将多个元组插入到一个表中。
 *
 * 这类似于 table_tuple_insert()，但在一次操作中插入多个元组。
 * 这通常比在循环中调用 table_tuple_insert() 更快，因为例如，
 * AM 可以减少 WAL 日志记录和页面锁定开销。
 *
 * 除了将 `nslots` 元组作为输入以及在 `slots` 中的 TupleTableSlots 数组， 
 * table_multi_insert() 的参数与 table_tuple_insert() 相同。
 *
 * 注意：这会将内存泄漏到当前内存上下文中。如果这是个问题，可以在
 * 调用此函数之前创建一个临时上下文。
 */
static inline void
table_multi_insert(Relation rel, TupleTableSlot **slots, int nslots,
				   CommandId cid, int options, struct BulkInsertStateData *bistate)
{
	rel->rd_tableam->multi_insert(rel, slots, nslots,
								  cid, options, bistate);
}

/*
 * 删除一个元组。
 *
 * 注意：除非准备处理并发更新条件，否则请勿直接调用此方法。 请改用 simple_table_tuple_delete。
 *
 * 输入参数：
 *	relation - 要修改的表（调用者必须持有适当的锁）
 *	tid - 要删除的元组的 TID
 *	cid - 删除命令 ID（用于可见性测试，成功时存储到 cmax 中）
 *	crosscheck - 如果不是 InvalidSnapshot，还检查该元组
 *	wait - 如果应等待任何冲突更新的提交/中止，则为 true
 * 输出参数：
 *	tmfd - 填充失败情况（见下文）
 *	changingPart - 仅当元组由于分区键的更新而被移动到另一个分区表时为 true。否则为 false。
 *
 * 正常的成功返回值是 TM_Ok，这意味着我们确实删除了它。
 * 失败返回代码是 TM_SelfModified、TM_Updated 和 TM_BeingModified（最后一种仅在 wait == false 时可能）。
 *
 * 在失败情况下，例程会用元组的 t_ctid、t_xmax 以及如果可能还有 t_cmax 填充 *tmfd。
 * 有关更多信息，请参见结构 TM_FailureData 的注释。
 */
static inline TM_Result
table_tuple_delete(Relation rel, ItemPointer tid, CommandId cid,
				   Snapshot snapshot, Snapshot crosscheck, bool wait,
				   TM_FailureData *tmfd, bool changingPart)
{
	return rel->rd_tableam->tuple_delete(rel, tid, cid,
										 snapshot, crosscheck,
										 wait, tmfd, changingPart);
}


/*
 * 更新一个元组。
 *
 * 注意：除非准备好处理并发更新条件，否则不要直接调用此函数。请使用 simple_table_tuple_update。
 *
 * 输入参数：
 *	relation - 要修改的表（调用者必须持有适当的锁）
 *	otid - 要替换的旧元组的 TID
 *	slot - 要存储的新构建的元组数据
 *	cid - 更新命令 ID（用于可见性测试，成功时存储到 cmax/cmin 中）
 *	crosscheck - 如果不是 InvalidSnapshot，还需检查旧元组
 *	wait - 如果应该等待任何冲突更新提交/中止，则为 true
 * 输出参数：
 *	tmfd - 在失败情况下填写（见下文）
 *	lockmode - 填充获得的元组锁模式
 *	update_indexes - 在成功情况下，如果此元组需要新的索引条目，则设置为 true
 *
 * 正常的成功返回值是 TM_Ok，这意味着我们确实更新了它。失败的返回代码是 TM_SelfModified、TM_Updated 和 TM_BeingModified（最后一个只有在 wait == false 时可能）。
 *
 * 成功时，slot 的 tts_tid 和 tts_tableOid 更新为匹配新存储的元组；特别是，slot->tts_tid 被设置为插入新元组的 TID，并且其 HEAP_ONLY_TUPLE 标志仅在执行 HOT 更新时设置。然而，任何在新元组数据中的 TOAST 更改不会反映在 *newtup 中。
 *
 * 在失败情况下，该例程用元组的 t_ctid、t_xmax 和（如果可能）t_cmax 填充 *tmfd。有关更多信息，请参阅 struct TM_FailureData 的注释。
 */
static inline TM_Result
table_tuple_update(Relation rel, ItemPointer otid, TupleTableSlot *slot,
				   CommandId cid, Snapshot snapshot, Snapshot crosscheck,
				   bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode,
				   bool *update_indexes)
{
	return rel->rd_tableam->tuple_update(rel, otid, slot,
										 cid, snapshot, crosscheck,
										 wait, tmfd,
										 lockmode, update_indexes);
}

/*
 * 以指定模式锁定一个元组。
 *
 * 输入参数：
 *	relation: 包含元组的关系（调用者必须持有适当的锁）
 *	tid: 要锁定的元组的 TID
 *	snapshot: 用于可见性判断的快照
 *	cid: 当前命令 ID（用于可见性测试，成功锁定后存储到元组的 cmax 中）
 *	mode: 所需的锁模式
 *	wait_policy: 如果元组锁不可用，应该怎么办
 *	flags:
 *		如果 TUPLE_LOCK_FLAG_LOCK_UPDATE_IN_PROGRESS，跟随更新链以在锁模式不冲突时也锁定后代元组。
 *		如果 TUPLE_LOCK_FLAG_FIND_LAST_VERSION，跟随更新链并锁定最新版本。
 *
 * 输出参数：
 *	*slot: 包含目标元组
 *	*tmfd: 在失败情况下填写（见下文）
 *
 * 函数结果可能是：
 *	TM_Ok: 锁成功获取
 *	TM_Invisible: 锁失败，因为元组对我们不可见
 *	TM_SelfModified: 锁失败，因为元组被自己更新
 *	TM_Updated: 锁失败，因为元组被其他事务更新
 *	TM_Deleted: 锁失败，因为元组被其他事务删除
 *	TM_WouldBlock: 锁无法获取，且 wait_policy 为跳过
 *
 * 在 TM_Invisible 和 TM_Deleted 之外的失败情况下，该例程会用元组的 t_ctid、t_xmax 和（如果可能）t_cmax 填充 *tmfd。有关更多信息，请参阅 struct TM_FailureData 的注释。
 */
static inline TM_Result
table_tuple_lock(Relation rel, ItemPointer tid, Snapshot snapshot,
				 TupleTableSlot *slot, CommandId cid, LockTupleMode mode,
				 LockWaitPolicy wait_policy, uint8 flags,
				 TM_FailureData *tmfd)
{
	return rel->rd_tableam->tuple_lock(rel, tid, snapshot, slot,
									   cid, mode, wait_policy,
									   flags, tmfd);
}

/*
 * 执行通过 tuple_insert 和带有指定 BulkInsertState 的 multi_insert 进行插入所需的操作。
 */
static inline void
table_finish_bulk_insert(Relation rel, int options)
{
	/* 可选回调 */
	if (rel->rd_tableam && rel->rd_tableam->finish_bulk_insert)
		rel->rd_tableam->finish_bulk_insert(rel, options);
}


/* ------------------------------------------------------------------------
 * DDL 相关功能。
 * ------------------------------------------------------------------------
 */

/*
 * 在 `newrnode` 中为 `rel` 创建存储，将持久性设置为 `persistence`。
 *
 * 此操作在关系创建和各种 DDL 操作期间用于创建可以完全填充的新 relfilenode。当为现有 relfilenode 创建新存储时，此函数应在 relcache 条目更新之前调用。
 *
 * *freezeXid, *minmulti 被设置为 pg_class.{relfrozenxid, relminmxid} 必须设置的表的 xid / multixact horizon。
 */
static inline void
table_relation_set_new_filenode(Relation rel,
								const RelFileNode *newrnode,
								char persistence,
								TransactionId *freezeXid,
								MultiXactId *minmulti)
{
	rel->rd_tableam->relation_set_new_filenode(rel, newrnode, persistence,
											   freezeXid, minmulti);
}

/*
 * 以非事务方式从 `rel` 中删除所有表内容。
 * 非事务方式意味着无需支持回滚。这通常仅用于执行当前事务中创建的 relfilenodes 的截断。
 */
static inline void
table_relation_nontransactional_truncate(Relation rel)
{
	rel->rd_tableam->relation_nontransactional_truncate(rel);
}

/*
 * 将数据从 `rel` 复制到新的 relfilenode `newrnode`。在调用此函数之前，新的 relfilenode 可能没有关联的存储。此操作仅应用于低级操作，例如更改关系的表空间。
 */
static inline void
table_relation_copy_data(Relation rel, const RelFileNode *newrnode)
{
	rel->rd_tableam->relation_copy_data(rel, newrnode);
}

/*
 * 将数据从`OldTable`复制到`NewTable`，作为CLUSTER或VACUUM FULL的一部分。
 *
 * 额外的输入参数：
 * - use_sort - 如果为true，则表内容按`OldIndex`适当排序；如果为false且OldIndex不是InvalidOid，则数据按该索引的顺序复制；如果为false且OldIndex是InvalidOid，则不执行排序
 * - OldIndex - 见use_sort
 * - OldestXmin - 由vacuum_set_xid_limits()计算，即使在关系的AM中不需要
 * - *xid_cutoff - 同上
 * - *multi_cutoff - 同上
 *
 * 输出参数：
 * - *xid_cutoff - rel的新relfrozenxid值，可能无效
 * - *multi_cutoff - rel的新relminmxid值，可能无效
 * - *tups_vacuumed - 统计信息，用于日志记录，如果适用于AM
 * - *tups_recently_dead - 统计信息，用于日志记录，如果适用于AM
 */
static inline void
table_relation_copy_for_cluster(Relation OldTable, Relation NewTable,
								Relation OldIndex,
								bool use_sort,
								TransactionId OldestXmin,
								TransactionId *xid_cutoff,
								MultiXactId *multi_cutoff,
								double *num_tuples,
								double *tups_vacuumed,
								double *tups_recently_dead)
{
	OldTable->rd_tableam->relation_copy_for_cluster(OldTable, NewTable, OldIndex,
													use_sort, OldestXmin,
													xid_cutoff, multi_cutoff,
													num_tuples, tups_vacuumed,
													tups_recently_dead);
}

/*
 * 对关系执行VACUUM。VACUUM可以由用户触发或由autovacuum触发。AM所执行的具体操作将严重依赖于个别AM。
 *
 * 进入时需要已经建立一个事务，并且表被ShareUpdateExclusive锁定。
 *
 * 请注意，VACUUM FULL（和CLUSTER）或ANALYZE都不经过此例程，即使（对于ANALYZE）它是同一VACUUM命令的一部分。
 */
static inline void
table_relation_vacuum(Relation rel, struct VacuumParams *params,
					  BufferAccessStrategy bstrategy)
{
	rel->rd_tableam->relation_vacuum(rel, params, bstrategy);
}

/*
 * 准备分析`scan`的块`blockno`。扫描需要已通过table_beginscan_analyze()启动。请注意，该例程可能会获取如锁等资源，这些资源在
 * table_scan_analyze_next_tuple()返回false之前将被保持。
 *
 * 如果块不适合采样则返回false，反之返回true。
 */
static inline bool
table_scan_analyze_next_block(TableScanDesc scan, BlockNumber blockno,
							  BufferAccessStrategy bstrategy)
{
	return scan->rs_rd->rd_tableam->scan_analyze_next_block(scan, blockno,
															bstrategy);
}

/*
 * 遍历在通过table_scan_analyze_next_block()选择的块中的元组（该方法需要返回true，并且此例程可能没有之前对同一块返回false）。如果找到一个适合采样的元组，则返回true并将元组存储在`slot`中。
 *
 * *liverows和*deadrows根据遇到的元组递增。
 */
static inline bool
table_scan_analyze_next_tuple(TableScanDesc scan, TransactionId OldestXmin,
							  double *liverows, double *deadrows,
							  TupleTableSlot *slot)
{
	return scan->rs_rd->rd_tableam->scan_analyze_next_tuple(scan, OldestXmin,
															liverows, deadrows,
															slot);
}

/*
 * table_index_build_scan - 扫描表以查找要索引的元组
 *
 * 这是从特定于访问方法的索引构建程序回调调用的，在AM完成其所需的所有设置后。扫描父表关系以查找应该输入索引的元组。每个
 * 这样的元组都将被传递给AM的回调例程，该例程将执行正确的操作以将其添加到新索引中。在我们返回后，AM的索引
 * 构建程序将执行其所需的任何清理工作。
 *
 * 返回活元组的总数。这是用于更新pg_class统计信息。（在这里无法做到这一点很令人烦恼，但我们想将该更新与其他更新合并；请参见index_update_stats。）请注意，索引AM本身必须跟踪索引元组的数量；我们在这里并不这样做，因为AM可能出于自己的原因拒绝一些元组，例如无法存储NULL。
 *
 * 如果“progress”，在开始扫描时更新PROGRESS_SCAN_BLOCKS_TOTAL计数器，随着进展更新PROGRESS_SCAN_BLOCKS_DONE。
 *
 * 一个副作用是如果我们检测到任何潜在的破损HOT链，则将indexInfo->ii_BrokenHotChain设置为true。目前，如果HOT链中有任何RECENTLY_DEAD或DELETE_IN_PROGRESS条目，我们将设置此项，而不努力检测它们是否真的与链尖不兼容。这对堆AM而言是有意义的，可能需要稍后推广到其他AM。
 */
static inline double
table_index_build_scan(Relation table_rel,
					   Relation index_rel,
					   struct IndexInfo *index_info,
					   bool allow_sync,
					   bool progress,
					   IndexBuildCallback callback,
					   void *callback_state,
					   TableScanDesc scan)
{
	return table_rel->rd_tableam->index_build_range_scan(table_rel,
														 index_rel,
														 index_info,
														 allow_sync,
														 false,
														 progress,
														 0,
														 InvalidBlockNumber,
														 callback,
														 callback_state,
														 scan);
}

/*
 * 与table_index_build_scan()相似，不同之处在于只扫描给定数量的块，而不是扫描整个表。通过传递InvalidBlockNumber作为numblocks来发出扫描到关系末尾的信号。请注意，当请求同步扫描时，无法限制扫描的范围。
 *
 * 当请求“anyvisible”模式时，所有对任何事务可见的元组都被索引并计为活元组，包括那些被仍在进行中的事务插入或删除的元组。
 */
static inline double
table_index_build_range_scan(Relation table_rel,
							 Relation index_rel,
							 struct IndexInfo *index_info,
							 bool allow_sync,
							 bool anyvisible,
							 bool progress,
							 BlockNumber start_blockno,
							 BlockNumber numblocks,
							 IndexBuildCallback callback,
							 void *callback_state,
							 TableScanDesc scan)
{
	return table_rel->rd_tableam->index_build_range_scan(table_rel,
														 index_rel,
														 index_info,
														 allow_sync,
														 anyvisible,
														 progress,
														 start_blockno,
														 numblocks,
														 callback,
														 callback_state,
														 scan);
}

/*
 * table_index_validate_scan - 并发索引构建的第二次表扫描
 *
 * 有关详细说明，请参见validate_index()。
 */
static inline void
table_index_validate_scan(Relation table_rel,
						  Relation index_rel,
						  struct IndexInfo *index_info,
						  Snapshot snapshot,
						  struct ValidateIndexState *state)
{
	table_rel->rd_tableam->index_validate_scan(table_rel,
											   index_rel,
											   index_info,
											   snapshot,
											   state);
}


/* ----------------------------------------------------------------------------
 * 其他功能
 * ----------------------------------------------------------------------------
 */


/*
 * 返回当前 `rel` 的大小（以字节为单位）。如果 `forkNumber` 是
 * InvalidForkNumber，返回关系的总体大小，否则返回指示的 fork 的大小。
 *
 * 请注意，对于某些 AM，总体大小可能并不等于各个 forks 的大小之和，
 * 例如，因为 AM 的存储不完全映射到内置的 fork 类型上。
 */
static inline uint64
table_relation_size(Relation rel, ForkNumber forkNumber)
{
	return rel->rd_tableam->relation_size(rel, forkNumber);
}

/*
 * table_relation_needs_toast_table - 这个关系是否需要 toast 表？
 */
static inline bool
table_relation_needs_toast_table(Relation rel)
{
	return rel->rd_tableam->relation_needs_toast_table(rel);
}

/*
 * 返回应用于实现此关系的 TOAST 表的 AM 的 OID。
 */
static inline Oid
table_relation_toast_am(Relation rel)
{
	return rel->rd_tableam->relation_toast_am(rel);
}

/*
 * 从 TOAST 表中获取整个或部分 TOAST 值。
 *
 * 如果此 AM 从未用于实现 TOAST 表，则不需要此回调。
 * 但是，如果曾在此类型的表中存储过 toast 值，则需要此回调。
 *
 * toastrel 是存储 toast 值的关系。
 *
 * valueid 标识要获取的 toast 值。对于堆来说，这对应于存储在
 * chunk_id 列中的值。
 *
 * attrsize 是要获取的 toast 值的总大小。
 *
 * sliceoffset 是要获取的 toast 值中第一个字节的偏移量。
 *
 * slicelength 是要获取的 toast 值的字节数。
 *
 * result 是调用者分配的存储获取字节的空间。
 */
static inline void
table_relation_fetch_toast_slice(Relation toastrel, Oid valueid,
								 int32 attrsize, int32 sliceoffset,
								 int32 slicelength, struct varlena *result)
{
	toastrel->rd_tableam->relation_fetch_toast_slice(toastrel, valueid,
													 attrsize,
													 sliceoffset, slicelength,
													 result);
}


/* ----------------------------------------------------------------------------
 * 计划相关功能
 * ----------------------------------------------------------------------------
 */

/*
 * 估算关系的当前大小，作为 estimate_rel_size() 的 AM 特定工作功能。
 * 有关参数的解释，请查看那里。
 */
static inline void
table_relation_estimate_size(Relation rel, int32 *attr_widths,
							 BlockNumber *pages, double *tuples,
							 double *allvisfrac)
{
	rel->rd_tableam->relation_estimate_size(rel, attr_widths, pages, tuples,
											allvisfrac);
}


/* ----------------------------------------------------------------------------
 * 执行器相关功能
 * ----------------------------------------------------------------------------
 */

/*
 * 准备从 `tbmres->blockno` 中获取/检查/返回元组，作为
 * bitmap 表扫描的一部分。 `scan` 必须通过
 * table_beginscan_bm() 开始。如果页面上没有找到元组，则返回 false，反之返回 true。
 *
 * 请注意，这是一个可选实现的函数，因此只有在验证存在（在计划时或其他）后方可使用。
 */
static inline bool
table_scan_bitmap_next_block(TableScanDesc scan,
							 struct TBMIterateResult *tbmres)
{
	/*
	 * 我们不期望直接调用具有有效CheckXidAlive的table_scan_bitmap_next_block用于目录或常规表。有关这些变量声明的详细注释，请参见xact.c。
	 */
	if (unlikely(TransactionIdIsValid(CheckXidAlive) && !bsysscan))
		elog(ERROR, "unexpected table_scan_bitmap_next_block call during logical decoding");

	return scan->rs_rd->rd_tableam->scan_bitmap_next_block(scan,
														   tbmres);
}

/*
 * 将 bitmap 表扫描的下一个元组获取到 `slot` 中，如果找到可见的元组则返回 true，否则返回 false。
 * table_scan_bitmap_next_block() 需要之前选择一个块（即返回 true），并且同一块的
 * 先前调用不能有 table_scan_bitmap_next_tuple() 返回 false。
 */
static inline bool
table_scan_bitmap_next_tuple(TableScanDesc scan,
							 struct TBMIterateResult *tbmres,
							 TupleTableSlot *slot)
{
	/*
	 * 我们不期望对具有有效 CheckXidAlive 的目录或常规表直接调用
	 * table_scan_bitmap_next_tuple。请参阅 xact.c 中这些变量声明的详细评论。
	 */
	if (unlikely(TransactionIdIsValid(CheckXidAlive) && !bsysscan))
		elog(ERROR, "unexpected table_scan_bitmap_next_tuple call during logical decoding");

	return scan->rs_rd->rd_tableam->scan_bitmap_next_tuple(scan,
														   tbmres,
														   slot);
}

/*
 * 准备从抽样扫描中的下一个块获取元组。如果抽样扫描已完成，则返回 false，反之返回 true。
 * `scan` 需要通过 table_beginscan_sampling() 开始。
 *
 * 如果必要，调用 TsmRoutine 的 NextSampleBlock() 回调（即 NextSampleBlock 不为 NULL），
 * 或对基础关系执行顺序扫描。
 */
static inline bool
table_scan_sample_next_block(TableScanDesc scan,
							 struct SampleScanState *scanstate)
{
	/*
	 * 我们不期望对具有有效 CheckXidAlive 的目录或常规表直接调用
	 * table_scan_sample_next_block。请参阅 xact.c 中这些变量声明的详细评论。
	 */
	if (unlikely(TransactionIdIsValid(CheckXidAlive) && !bsysscan))
		elog(ERROR, "unexpected table_scan_sample_next_block call during logical decoding");
	return scan->rs_rd->rd_tableam->scan_sample_next_block(scan, scanstate);
}

/*
 * 将下一个样本元组获取到 `slot` 中，如果找到可见的元组，则返回 true，否则返回 false。
 * table_scan_sample_next_block() 需要之前选择一个块（即返回 true），并且同一块的
 * 先前调用不能有 table_scan_sample_next_tuple() 返回 false。
 *
 * 这将调用 TsmRoutine 的 NextSampleTuple() 回调。
 */
static inline bool
table_scan_sample_next_tuple(TableScanDesc scan,
							 struct SampleScanState *scanstate,
							 TupleTableSlot *slot)
{
	/*
	 * 我们不期望对具有有效 CheckXidAlive 的目录或常规表直接调用
	 * table_scan_sample_next_tuple。请参阅 xact.c 中这些变量声明的详细评论。
	 */
	if (unlikely(TransactionIdIsValid(CheckXidAlive) && !bsysscan))
		elog(ERROR, "unexpected table_scan_sample_next_tuple call during logical decoding");
	return scan->rs_rd->rd_tableam->scan_sample_next_tuple(scan, scanstate,
														   slot);
}


/* ----------------------------------------------------------------------------
 * 用于简化修改的函数。
 * ----------------------------------------------------------------------------
 */

extern void simple_table_tuple_insert(Relation rel, TupleTableSlot *slot);
extern void simple_table_tuple_delete(Relation rel, ItemPointer tid,
									  Snapshot snapshot);
extern void simple_table_tuple_update(Relation rel, ItemPointer otid,
									  TupleTableSlot *slot, Snapshot snapshot,
									  bool *update_indexes);


/* ----------------------------------------------------------------------------
 * 实现块导向 AM 的并行扫描的辅助函数。
 * ----------------------------------------------------------------------------
 */

extern Size table_block_parallelscan_estimate(Relation rel);
extern Size table_block_parallelscan_initialize(Relation rel,
												ParallelTableScanDesc pscan);
extern void table_block_parallelscan_reinitialize(Relation rel,
												  ParallelTableScanDesc pscan);
extern BlockNumber table_block_parallelscan_nextpage(Relation rel,
													 ParallelBlockTableScanWorker pbscanwork,
													 ParallelBlockTableScanDesc pbscan);
extern void table_block_parallelscan_startblock_init(Relation rel,
													 ParallelBlockTableScanWorker pbscanwork,
													 ParallelBlockTableScanDesc pbscan);


/* ----------------------------------------------------------------------------
 * 辅助函数，用于实现基于块的 AM 的关系大小。
 * ----------------------------------------------------------------------------
 */

extern uint64 table_block_relation_size(Relation rel, ForkNumber forkNumber);
extern void table_block_relation_estimate_size(Relation rel,
											   int32 *attr_widths,
											   BlockNumber *pages,
											   double *tuples,
											   double *allvisfrac,
											   Size overhead_bytes_per_tuple,
											   Size usable_bytes_per_page);

/* ----------------------------------------------------------------------------
 * 在 tableamapi.c 中的函数
 * ----------------------------------------------------------------------------
 */

extern const TableAmRoutine *GetTableAmRoutine(Oid amhandler);
extern const TableAmRoutine *GetHeapamTableAmRoutine(void);
extern bool check_default_table_access_method(char **newval, void **extra,
											  GucSource source);

#endif							/* TABLEAM_H */
